<?php

/**
 * Copyright (c) 2006- Facebook
 * Distributed under the Thrift Software License
 *
 * See accompanying file LICENSE or visit the Thrift site at:
 * http://developers.facebook.com/thrift/
 *
 * @package thrift
 * @author Mark Slee <mcslee@facebook.com>
 */

/**
 * Data types that can be sent via Thrift
 */
class TType {
  const STOP   = 0;
  const VOID   = 1;
  const BOOL   = 2;
  const BYTE   = 3;
  const I08	= 3;
  const DOUBLE = 4;
  const I16	= 6;
  const I32	= 8;
  const I64	= 10;
  const STRING = 11;
  const UTF7   = 11;
  const STRUCT = 12;
  const MAP	= 13;
  const SET	= 14;
  const LST	= 15;	// N.B. cannot use LIST keyword in PHP!
  const UTF8   = 16;
  const UTF16  = 17;
}

/**
 * Message types for RPC
 */
class TMessageType {
  const CALL  = 1;
  const REPLY = 2;
  const EXCEPTION = 3;
}

/**
 * NOTE(mcslee): This currently contains a ton of duplicated code from TBase
 * because we need to save CPU cycles and this is not yet in an extension.
 * Ideally we'd multiply-inherit TException from both Exception and Base, but
 * that's not possible in PHP and there are no modules either, so for now we
 * apologetically take a trip to HackTown.
 *
 * Can be called with standard Exception constructor (message, code) or with
 * Thrift Base object constructor (spec, vals).
 *
 * @param mixed $p1 Message (string) or type-spec (array)
 * @param mixed $p2 Code (integer) or values (array)
 */
class TException extends Exception {
  function __construct($p1=null, $p2=0) {
	if (is_array($p1) && is_array($p2)) {
	  $spec = $p1;
	  $vals = $p2;
	  foreach ($spec as $fid => $fspec) {
		$var = $fspec['var'];
		if (isset($vals[$var])) {
		  $this->$var = $vals[$var];
		}
	  }
	} else {
	  parent::__construct($p1, $p2);
	}
  }

  static $tmethod = array(TType::BOOL   => 'Bool',
						  TType::BYTE   => 'Byte',
						  TType::I16	=> 'I16',
						  TType::I32	=> 'I32',
						  TType::I64	=> 'I64',
						  TType::DOUBLE => 'Double',
						  TType::STRING => 'String');

  private function _readMap(&$var, $spec, $input) {
	$xfer = 0;
	$ktype = $spec['ktype'];
	$vtype = $spec['vtype'];
	$kread = $vread = null;
	if (isset(TBase::$tmethod[$ktype])) {
	  $kread = 'read'.TBase::$tmethod[$ktype];
	} else {
	  $kspec = $spec['key'];
	}
	if (isset(TBase::$tmethod[$vtype])) {
	  $vread = 'read'.TBase::$tmethod[$vtype];
	} else {
	  $vspec = $spec['val'];
	}
	$var = array();
	$_ktype = $_vtype = $size = 0;
	$xfer += $input->readMapBegin($_ktype, $_vtype, $size);
	for ($i = 0; $i < $size; ++$i) {
	  $key = $val = null;
	  if ($kread !== null) {
		$xfer += $input->$kread($key);
	  } else {
		switch ($ktype) {
		case TType::STRUCT:
		  $class = $kspec['class'];
		  $key = new $class();
		  $xfer += $key->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($key, $kspec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($key, $kspec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($key, $kspec, $input, true);
		  break;
		}
	  }
	  if ($vread !== null) {
		$xfer += $input->$vread($val);
	  } else {
		switch ($vtype) {
		case TType::STRUCT:
		  $class = $vspec['class'];
		  $val = new $class();
		  $xfer += $val->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($val, $vspec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($val, $vspec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($val, $vspec, $input, true);
		  break;
		}
	  }
	  $var[$key] = $val;
	}
	$xfer += $input->readMapEnd();
	return $xfer;
  }

  private function _readList(&$var, $spec, $input, $set=false) {
	$xfer = 0;
	$etype = $spec['etype'];
	$eread = $vread = null;
	if (isset(TBase::$tmethod[$etype])) {
	  $eread = 'read'.TBase::$tmethod[$etype];
	} else {
	  $espec = $spec['elem'];
	}
	$var = array();
	$_etype = $size = 0;
	if ($set) {
	  $xfer += $input->readSetBegin($_etype, $size);
	} else {
	  $xfer += $input->readListBegin($_etype, $size);
	}
	for ($i = 0; $i < $size; ++$i) {
	  $elem = null;
	  if ($eread !== null) {
		$xfer += $input->$eread($elem);
	  } else {
		$espec = $spec['elem'];
		switch ($etype) {
		case TType::STRUCT:
		  $class = $espec['class'];
		  $elem = new $class();
		  $xfer += $elem->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($elem, $espec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($elem, $espec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($elem, $espec, $input, true);
		  break;
		}
	  }
	  if ($set) {
		$var[$elem] = true;
	  } else {
		$var []= $elem;
	  }
	}
	if ($set) {
	  $xfer += $input->readSetEnd();
	} else {
	  $xfer += $input->readListEnd();
	}
	return $xfer;
  }

  protected function _read($class, $spec, $input) {
	$xfer = 0;
	$fname = null;
	$ftype = 0;
	$fid = 0;
	$xfer += $input->readStructBegin($fname);
	while (true) {
	  $xfer += $input->readFieldBegin($fname, $ftype, $fid);
	  if ($ftype == TType::STOP) {
		break;
	  }
	  if (isset($spec[$fid])) {
		$fspec = $spec[$fid];
		$var = $fspec['var'];
		if ($ftype == $fspec['type']) {
		  $xfer = 0;
		  if (isset(TBase::$tmethod[$ftype])) {
			$func = 'read'.TBase::$tmethod[$ftype];
			$xfer += $input->$func($this->$var);
		  } else {
			switch ($ftype) {
			case TType::STRUCT:
			  $class = $fspec['class'];
			  $this->$var = new $class();
			  $xfer += $this->$var->read($input);
			  break;
			case TType::MAP:
			  $xfer += $this->_readMap($this->$var, $fspec, $input);
			  break;
			case TType::LST:
			  $xfer += $this->_readList($this->$var, $fspec, $input, false);
			  break;
			case TType::SET:
			  $xfer += $this->_readList($this->$var, $fspec, $input, true);
			  break;
			}
		  }
		} else {
		  $xfer += $input->skip($ftype);
		}
	  } else {
		$xfer += $input->skip($ftype);
	  }
	  $xfer += $input->readFieldEnd();
	}
	$xfer += $input->readStructEnd();
	return $xfer;
  }

  private function _writeMap($var, $spec, $output) {
	$xfer = 0;
	$ktype = $spec['ktype'];
	$vtype = $spec['vtype'];
	$kwrite = $vwrite = null;
	if (isset(TBase::$tmethod[$ktype])) {
	  $kwrite = 'write'.TBase::$tmethod[$ktype];
	} else {
	  $kspec = $spec['key'];
	}
	if (isset(TBase::$tmethod[$vtype])) {
	  $vwrite = 'write'.TBase::$tmethod[$vtype];
	} else {
	  $vspec = $spec['val'];
	}
	$xfer += $output->writeMapBegin($ktype, $vtype, count($var));
	foreach ($var as $key => $val) {
	  if (isset($kwrite)) {
		$xfer += $output->$kwrite($key);
	  } else {
		switch ($ktype) {
		case TType::STRUCT:
		  $xfer += $key->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($key, $kspec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($key, $kspec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($key, $kspec, $output, true);
		  break;
		}
	  }
	  if (isset($vwrite)) {
		$xfer += $output->$vwrite($val);
	  } else {
		switch ($vtype) {
		case TType::STRUCT:
		  $xfer += $val->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($val, $vspec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($val, $vspec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($val, $vspec, $output, true);
		  break;
		}
	  }
	}
	$xfer += $output->writeMapEnd();
	return $xfer;
  }

  private function _writeList($var, $spec, $output, $set=false) {
	$xfer = 0;
	$etype = $spec['etype'];
	$ewrite = null;
	if (isset(TBase::$tmethod[$etype])) {
	  $ewrite = 'write'.TBase::$tmethod[$etype];
	} else {
	  $espec = $spec['elem'];
	}
	if ($set) {
	  $xfer += $output->writeSetBegin($etype, count($var));
	} else {
	  $xfer += $output->writeListBegin($etype, count($var));
	}
	foreach ($var as $key => $val) {
	  $elem = $set ? $key : $val;
	  if (isset($ewrite)) {
		$xfer += $output->$ewrite($elem);
	  } else {
		switch ($etype) {
		case TType::STRUCT:
		  $xfer += $elem->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($elem, $espec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($elem, $espec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($elem, $espec, $output, true);
		  break;
		}
	  }
	}
	if ($set) {
	  $xfer += $output->writeSetEnd();
	} else {
	  $xfer += $output->writeListEnd();
	}
	return $xfer;
  }

  protected function _write($class, $spec, $output) {
	$xfer = 0;
	$xfer += $output->writeStructBegin($class);
	foreach ($spec as $fid => $fspec) {
	  $var = $fspec['var'];
	  if ($this->$var !== null) {
		$ftype = $fspec['type'];
		$xfer += $output->writeFieldBegin($var, $ftype, $fid);
		if (isset(TBase::$tmethod[$ftype])) {
		  $func = 'write'.TBase::$tmethod[$ftype];
		  $xfer += $output->$func($this->$var);
		} else {
		  switch ($ftype) {
		  case TType::STRUCT:
			$xfer += $this->$var->write($output);
			break;
		  case TType::MAP:
			$xfer += $this->_writeMap($this->$var, $fspec, $output);
			break;
		  case TType::LST:
			$xfer += $this->_writeList($this->$var, $fspec, $output, false);
			break;
		  case TType::SET:
			$xfer += $this->_writeList($this->$var, $fspec, $output, true);
			break;
		  }
		}
		$xfer += $output->writeFieldEnd();
	  }
	}
	$xfer += $output->writeFieldStop();
	$xfer += $output->writeStructEnd();
	return $xfer;
  }

}

/**
 * Base class from which other Thrift structs extend. This is so that we can
 * cut back on the size of the generated code which is turning out to have a
 * nontrivial cost just to load thanks to the wondrously abysmal implementation
 * of PHP. Note that code is intentionally duplicated in here to avoid making
 * function calls for every field or member of a container..
 */
abstract class TBase {

  static $tmethod = array(TType::BOOL   => 'Bool',
						  TType::BYTE   => 'Byte',
						  TType::I16	=> 'I16',
						  TType::I32	=> 'I32',
						  TType::I64	=> 'I64',
						  TType::DOUBLE => 'Double',
						  TType::STRING => 'String');

  abstract function read($input);

  abstract function write($output);

  public function __construct($spec=null, $vals=null) {
	if (is_array($spec) && is_array($vals)) {
	  foreach ($spec as $fid => $fspec) {
		$var = $fspec['var'];
		if (isset($vals[$var])) {
		  $this->$var = $vals[$var];
		}
	  }
	}
  }

  private function _readMap(&$var, $spec, $input) {
	$xfer = 0;
	$ktype = $spec['ktype'];
	$vtype = $spec['vtype'];
	$kread = $vread = null;
	if (isset(TBase::$tmethod[$ktype])) {
	  $kread = 'read'.TBase::$tmethod[$ktype];
	} else {
	  $kspec = $spec['key'];
	}
	if (isset(TBase::$tmethod[$vtype])) {
	  $vread = 'read'.TBase::$tmethod[$vtype];
	} else {
	  $vspec = $spec['val'];
	}
	$var = array();
	$_ktype = $_vtype = $size = 0;
	$xfer += $input->readMapBegin($_ktype, $_vtype, $size);
	for ($i = 0; $i < $size; ++$i) {
	  $key = $val = null;
	  if ($kread !== null) {
		$xfer += $input->$kread($key);
	  } else {
		switch ($ktype) {
		case TType::STRUCT:
		  $class = $kspec['class'];
		  $key = new $class();
		  $xfer += $key->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($key, $kspec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($key, $kspec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($key, $kspec, $input, true);
		  break;
		}
	  }
	  if ($vread !== null) {
		$xfer += $input->$vread($val);
	  } else {
		switch ($vtype) {
		case TType::STRUCT:
		  $class = $vspec['class'];
		  $val = new $class();
		  $xfer += $val->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($val, $vspec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($val, $vspec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($val, $vspec, $input, true);
		  break;
		}
	  }
	  $var[$key] = $val;
	}
	$xfer += $input->readMapEnd();
	return $xfer;
  }

  private function _readList(&$var, $spec, $input, $set=false) {
	$xfer = 0;
	$etype = $spec['etype'];
	$eread = $vread = null;
	if (isset(TBase::$tmethod[$etype])) {
	  $eread = 'read'.TBase::$tmethod[$etype];
	} else {
	  $espec = $spec['elem'];
	}
	$var = array();
	$_etype = $size = 0;
	if ($set) {
	  $xfer += $input->readSetBegin($_etype, $size);
	} else {
	  $xfer += $input->readListBegin($_etype, $size);
	}
	for ($i = 0; $i < $size; ++$i) {
	  $elem = null;
	  if ($eread !== null) {
		$xfer += $input->$eread($elem);
	  } else {
		$espec = $spec['elem'];
		switch ($etype) {
		case TType::STRUCT:
		  $class = $espec['class'];
		  $elem = new $class();
		  $xfer += $elem->read($input);
		  break;
		case TType::MAP:
		  $xfer += $this->_readMap($elem, $espec, $input);
		  break;
		case TType::LST:
		  $xfer += $this->_readList($elem, $espec, $input, false);
		  break;
		case TType::SET:
		  $xfer += $this->_readList($elem, $espec, $input, true);
		  break;
		}
	  }
	  if ($set) {
		$var[$elem] = true;
	  } else {
		$var []= $elem;
	  }
	}
	if ($set) {
	  $xfer += $input->readSetEnd();
	} else {
	  $xfer += $input->readListEnd();
	}
	return $xfer;
  }

  protected function _read($class, $spec, $input) {
	$xfer = 0;
	$fname = null;
	$ftype = 0;
	$fid = 0;
	$xfer += $input->readStructBegin($fname);
	while (true) {
	  $xfer += $input->readFieldBegin($fname, $ftype, $fid);
	  if ($ftype == TType::STOP) {
		break;
	  }
	  if (isset($spec[$fid])) {
		$fspec = $spec[$fid];
		$var = $fspec['var'];
		if ($ftype == $fspec['type']) {
		  $xfer = 0;
		  if (isset(TBase::$tmethod[$ftype])) {
			$func = 'read'.TBase::$tmethod[$ftype];
			$xfer += $input->$func($this->$var);
		  } else {
			switch ($ftype) {
			case TType::STRUCT:
			  $class = $fspec['class'];
			  $this->$var = new $class();
			  $xfer += $this->$var->read($input);
			  break;
			case TType::MAP:
			  $xfer += $this->_readMap($this->$var, $fspec, $input);
			  break;
			case TType::LST:
			  $xfer += $this->_readList($this->$var, $fspec, $input, false);
			  break;
			case TType::SET:
			  $xfer += $this->_readList($this->$var, $fspec, $input, true);
			  break;
			}
		  }
		} else {
		  $xfer += $input->skip($ftype);
		}
	  } else {
		$xfer += $input->skip($ftype);
	  }
	  $xfer += $input->readFieldEnd();
	}
	$xfer += $input->readStructEnd();
	return $xfer;
  }

  private function _writeMap($var, $spec, $output) {
	$xfer = 0;
	$ktype = $spec['ktype'];
	$vtype = $spec['vtype'];
	$kwrite = $vwrite = null;
	if (isset(TBase::$tmethod[$ktype])) {
	  $kwrite = 'write'.TBase::$tmethod[$ktype];
	} else {
	  $kspec = $spec['key'];
	}
	if (isset(TBase::$tmethod[$vtype])) {
	  $vwrite = 'write'.TBase::$tmethod[$vtype];
	} else {
	  $vspec = $spec['val'];
	}
	$xfer += $output->writeMapBegin($ktype, $vtype, count($var));
	foreach ($var as $key => $val) {
	  if (isset($kwrite)) {
		$xfer += $output->$kwrite($key);
	  } else {
		switch ($ktype) {
		case TType::STRUCT:
		  $xfer += $key->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($key, $kspec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($key, $kspec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($key, $kspec, $output, true);
		  break;
		}
	  }
	  if (isset($vwrite)) {
		$xfer += $output->$vwrite($val);
	  } else {
		switch ($vtype) {
		case TType::STRUCT:
		  $xfer += $val->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($val, $vspec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($val, $vspec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($val, $vspec, $output, true);
		  break;
		}
	  }
	}
	$xfer += $output->writeMapEnd();
	return $xfer;
  }

  private function _writeList($var, $spec, $output, $set=false) {
	$xfer = 0;
	$etype = $spec['etype'];
	$ewrite = null;
	if (isset(TBase::$tmethod[$etype])) {
	  $ewrite = 'write'.TBase::$tmethod[$etype];
	} else {
	  $espec = $spec['elem'];
	}
	if ($set) {
	  $xfer += $output->writeSetBegin($etype, count($var));
	} else {
	  $xfer += $output->writeListBegin($etype, count($var));
	}
	foreach ($var as $key => $val) {
	  $elem = $set ? $key : $val;
	  if (isset($ewrite)) {
		$xfer += $output->$ewrite($elem);
	  } else {
		switch ($etype) {
		case TType::STRUCT:
		  $xfer += $elem->write($output);
		  break;
		case TType::MAP:
		  $xfer += $this->_writeMap($elem, $espec, $output);
		  break;
		case TType::LST:
		  $xfer += $this->_writeList($elem, $espec, $output, false);
		  break;
		case TType::SET:
		  $xfer += $this->_writeList($elem, $espec, $output, true);
		  break;
		}
	  }
	}
	if ($set) {
	  $xfer += $output->writeSetEnd();
	} else {
	  $xfer += $output->writeListEnd();
	}
	return $xfer;
  }

  protected function _write($class, $spec, $output) {
	$xfer = 0;
	$xfer += $output->writeStructBegin($class);
	foreach ($spec as $fid => $fspec) {
	  $var = $fspec['var'];
	  if ($this->$var !== null) {
		$ftype = $fspec['type'];
		$xfer += $output->writeFieldBegin($var, $ftype, $fid);
		if (isset(TBase::$tmethod[$ftype])) {
		  $func = 'write'.TBase::$tmethod[$ftype];
		  $xfer += $output->$func($this->$var);
		} else {
		  switch ($ftype) {
		  case TType::STRUCT:
			$xfer += $this->$var->write($output);
			break;
		  case TType::MAP:
			$xfer += $this->_writeMap($this->$var, $fspec, $output);
			break;
		  case TType::LST:
			$xfer += $this->_writeList($this->$var, $fspec, $output, false);
			break;
		  case TType::SET:
			$xfer += $this->_writeList($this->$var, $fspec, $output, true);
			break;
		  }
		}
		$xfer += $output->writeFieldEnd();
	  }
	}
	$xfer += $output->writeFieldStop();
	$xfer += $output->writeStructEnd();
	return $xfer;
  }
}

class TApplicationException extends TException {
  static $_TSPEC =
	array(1 => array('var' => 'message',
					 'type' => TType::STRING),
		  2 => array('var' => 'code',
					 'type' => TType::I32));

  const UNKNOWN = 0;
  const UNKNOWN_METHOD = 1;
  const INVALID_MESSAGE_TYPE = 2;
  const WRONG_METHOD_NAME = 3;
  const BAD_SEQUENCE_ID = 4;
  const MISSING_RESULT = 5;

  function __construct($message=null, $code=0) {
	parent::__construct($message, $code);
  }

  public function read($output) {
	return $this->_read('TApplicationException', self::$_TSPEC, $output);
  }

  public function write($output) {
	$xfer = 0;
	$xfer += $output->writeStructBegin('TApplicationException');
	$message = $this->getMessage();
	if ($message) {
	  $xfer += $output->writeFieldBegin('message', TType::STRING, 1);
	  $xfer += $output->writeString($message);
	  $xfer += $output->writeFieldEnd();
	}
	$code = $this->getCode();
	if ($code) {
	  $xfer += $output->writeFieldBegin('type', TType::I32, 2);
	  $xfer += $output->writeI32($code);
	  $xfer += $output->writeFieldEnd();
	}
	$xfer += $output->writeFieldStop();
	$xfer += $output->writeStructEnd();
	return $xfer;
  }
}

/**
 * Set global THRIFT ROOT automatically via inclusion here
 */
if (!isset($GLOBALS['THRIFT_ROOT'])) {
  $GLOBALS['THRIFT_ROOT'] = dirname(__FILE__);
}
include_once $GLOBALS['THRIFT_ROOT'].'/protocol/TProtocol.php';
include_once $GLOBALS['THRIFT_ROOT'].'/transport/TTransport.php';

?>
